package com.ibeeking.gmc.global.g.rest.service.impl;

import com.ibeeking.gmc.global.g.rest.service.IAreaService;
import com.ibeeking.found.common.base.BaseDO;
import com.ibeeking.found.common.entity.AreaDTO;
import com.ibeeking.found.common.entity.AreaSimpleDTO;
import com.ibeeking.found.common.enums.PublishStatusEnum;
import com.ibeeking.gmc.global.api.common.query.AreaQuery;
import com.ibeeking.gmc.global.service.common.bo.AreaBO;
import com.ibeeking.gmc.global.service.common.constants.RedisKeyConstant;
import com.ibeeking.gmc.global.service.common.dos.AreaDO;
import com.ibeeking.gmc.global.service.common.param.AreaParam;
import com.ibeeking.gmc.global.service.common.query.AreaPageQuery;
import com.ibeeking.gmc.global.service.common.vo.AreaPageVO;
import com.ibeeking.gmc.global.service.common.vo.AreaTreeVO;
import com.ibeeking.gmc.global.service.common.vo.AreaVO;
import com.ibeeking.gmc.global.service.mapper.AreaMapper;
import com.ibeeking.nematos.core.utils.SpringBeanUtil;
import com.ibeeking.nematos.exception.BusinessException;
import com.ibeeking.nematos.redis.annotation.CacheQuery;
import com.ibeeking.nematos.redis.utils.RedisUtils;
import com.ibeeking.nematos.utils.bean.BeanUtil;
import com.ibeeking.nematos.utils.data.Collections3;
import com.ibeeking.nematos.utils.data.StringUtils;
import com.ibeeking.nematos.utils.data.TreeFastUtils;
import com.ibeeking.nematos.utils.json.JsonUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @ClassName AreaServiceImpl
 * @Description
 * @Author ibeeking
 * @Date 2020-12-23 13:43
 **/
@Service
public class AreaServiceImpl extends ServiceImpl<AreaMapper, AreaDO> implements IAreaService {

    private static final String PARENT_ROOT_CODE = "000000";

    @Resource
    private AreaMapper areaMapper;

    @Resource
    private RedisUtils redisUtils;

    @Override
    public Page<AreaPageVO> pageList(AreaPageQuery areaPageQuery) {
        LambdaQueryWrapper<AreaDO> queryWrapper = new LambdaQueryWrapper<>(AreaDO.class);
        queryWrapper
                .eq(AreaDO::getParentCode, areaPageQuery.getParentCode())
                .orderByAsc(AreaDO::getSort)
                .orderByDesc(BaseDO::getCreateTime)
        ;

        queryWrapper.eq(null != areaPageQuery.getPublishStatus(), AreaDO::getPublishStatus, areaPageQuery.getPublishStatus());

        if (StringUtils.isNotBlank(areaPageQuery.getCode())) {
            queryWrapper.likeRight(AreaDO::getCode, areaPageQuery.getCode());
        }

        if (StringUtils.isNotBlank(areaPageQuery.getName())) {
            queryWrapper.like(AreaDO::getName, areaPageQuery.getName());
        }

        if (StringUtils.isNotBlank(areaPageQuery.getShortName())) {
            queryWrapper.like(AreaDO::getShortName, areaPageQuery.getShortName());
        }

        Page<AreaDO> page = areaMapper.selectPage(new Page<>(areaPageQuery.getPageNum(), areaPageQuery.getPageSize()), queryWrapper);
        return BeanUtil.convertPage(page, AreaPageVO.class);
    }

    @Override
    public Boolean add(AreaParam areaParam) {
        Long id = getBean().checkCode(areaParam.getCode(), areaParam.getParentCode());
        if (null != id) {
            throw new BusinessException("当前地区编码已存在");
        }
        AreaDO areaDO = BeanUtil.convertBean(areaParam, AreaDO.class);
        if (PARENT_ROOT_CODE.equals(areaDO.getParentCode())) {
            areaDO.setLevel(1);
        } else {
            //查询父级目录级别
            AreaBO areaBO = getBean().queryByCode(areaDO.getParentCode());
            areaDO.setLevel(areaBO.getLevel() + 1);
        }
        areaDO.setCreateTime(LocalDateTime.now());
        areaDO.setModifyTime(LocalDateTime.now());
        return areaMapper.insert(areaDO) > 0;
    }

    @Override
    public Boolean modify(AreaParam areaParam) {
        Long id = getBean().checkCode(areaParam.getCode(), areaParam.getParentCode());
        if (null != id && !id.equals(areaParam.getId())) {
            throw new BusinessException("当前地区编码已存在");
        }
        AreaDO areaDO = BeanUtil.convertBean(areaParam, AreaDO.class);
        areaParam.setParentCode(null);
        areaDO.setModifyTime(LocalDateTime.now());
        return areaMapper.updateById(areaDO) > 0;
    }

    @Override
    public AreaVO view(Long id) {
        return BeanUtil.convertBean(getBean().queryById(id), AreaVO.class);
    }

    @Override
    public AreaVO areaInfoByCode(String code) {
        AreaDO areaDO = areaMapper.selectOne(new LambdaQueryWrapper<>(AreaDO.class).eq(AreaDO::getCode, code));
        return BeanUtil.convertBean(areaDO, AreaVO.class);
    }

    @Override
    public AreaBO queryById(Long id) {
        AreaDO areaDO = areaMapper.selectById(id);
        return BeanUtil.convertBean(areaDO, AreaBO.class);
    }

    @Override
    public AreaBO queryByCode(String code) {
        AreaDO areaDO = areaMapper.selectOne(new LambdaQueryWrapper<>(AreaDO.class).eq(AreaDO::getCode, code));
        return BeanUtil.convertBean(areaDO, AreaBO.class);
    }

    @Override
    public Boolean changeStatus(Long id, Integer status, Long userId) {
        AreaDO areaDO = new AreaDO();
        areaDO.setId(id);
        areaDO.setPublishStatus(status);
        areaDO.setModifyBy(userId);
        areaDO.setModifyTime(LocalDateTime.now());
        return areaMapper.updateById(areaDO) > 0;
    }

    @Override
    public Boolean del(Long id) {
        return areaMapper.deleteById(id) > 0;
    }

    @Override
    public Long checkCode(String code, String parentCode) {
        AreaDO areaDO = areaMapper.selectOne(new LambdaQueryWrapper<>(AreaDO.class).eq(AreaDO::getCode, code).eq(AreaDO::getParentCode, parentCode));
        return null != areaDO ? areaDO.getId() : null;
    }

    @Override
    public List<AreaPageVO> listByParentCode(String parentCode) {
        LambdaQueryWrapper<AreaDO> queryWrapper = new LambdaQueryWrapper<>(AreaDO.class)
                .eq(AreaDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue())
                .eq(AreaDO::getParentCode, parentCode)
                .orderByAsc(AreaDO::getSort)
                .orderByDesc(BaseDO::getCreateTime);
        List<AreaDO> areaDOS = areaMapper.selectList(queryWrapper);
        if (Collections3.isEmpty(areaDOS)) {
            return Collections3.emptyList();
        }
        return JsonUtils.jsonToList(areaDOS, AreaPageVO.class);
    }

    @Override
    public List<AreaTreeVO> treeListByParentCode(String parentCode) {
        List<AreaDO> temp = new ArrayList<>();
        List<String> parentCodes = new ArrayList<>();
        parentCodes.add(parentCode);
        Boolean flag = true;
        while (flag) {
            LambdaQueryWrapper<AreaDO> queryWrapper = new LambdaQueryWrapper<>(AreaDO.class)
                    .eq(AreaDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue())
                    .in(AreaDO::getParentCode, parentCodes)
                    .orderByAsc(AreaDO::getSort)
                    .orderByDesc(BaseDO::getCreateTime);
            List<AreaDO> areaDOS = areaMapper.selectList(queryWrapper);
            if (Collections3.isEmpty(areaDOS)) {
                flag = false;
                break;
            } else {
                temp.addAll(areaDOS);
                parentCodes.clear();
                parentCodes.addAll(areaDOS.stream().map(AreaDO::getCode).collect(Collectors.toList()));
            }
        }
        List<AreaTreeVO> result = JsonUtils.jsonToList(temp, AreaTreeVO.class);
        result.parallelStream().forEach(areaTreeVO -> {
            areaTreeVO.setId(areaTreeVO.getCode());
            areaTreeVO.setParentId(areaTreeVO.getParentCode());
        });
        return TreeFastUtils.listToTree(result, parentCode);
    }

    @Override
    public List<AreaPageVO> treeList(String parentCode) {
        LambdaQueryWrapper<AreaDO> queryWrapper = new LambdaQueryWrapper<>(AreaDO.class);
        queryWrapper.eq(AreaDO::getParentCode, parentCode)
                .orderByAsc(AreaDO::getSort)
                .orderByDesc(BaseDO::getCreateTime);
        List<AreaDO> areaDOS = areaMapper.selectList(queryWrapper);
        if (Collections3.isEmpty(areaDOS)) {
            return Collections3.emptyList();
        }
        return JsonUtils.jsonToList(areaDOS, AreaPageVO.class);
    }

    @CacheQuery(cacheName = RedisKeyConstant.AREA, key = "#code", expires = RedisKeyConstant.MONTH)
    @Override
    public AreaDTO queryName(String code) {
        List<AreaDO> areaDOS = areaMapper.selectList(new LambdaQueryWrapper<>(AreaDO.class).eq(AreaDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue()).eq(AreaDO::getCode, code));
        if (!Collections3.isEmpty(areaDOS)) {
            return BeanUtil.convertBean(areaDOS.get(0), AreaDTO.class);
        }
        return null;
    }

    @Override
    public AreaDTO queryAreaName(Long id) {
        AreaDO areaDO = areaMapper.selectById(id);
        return BeanUtil.convertBean(areaDO,AreaDTO.class);
    }

    @Override
    public List<AreaDTO> queryNames(List<String> codes) {
        List<AreaDTO> result = new ArrayList<>();
        codes.stream().distinct().forEach(code -> {
            Optional.ofNullable(getBean().queryName(code)).ifPresent(result::add);
        });
        return result;
    }

    @Override
    public List<AreaDTO> queryNamesByParentCode(String parentCode) {
        List<AreaDO> temp = new ArrayList<>();
        List<String> parentCodes = new ArrayList<>();
        parentCodes.add(parentCode);
        Boolean flag = true;
        while (flag) {
            LambdaQueryWrapper<AreaDO> queryWrapper = new LambdaQueryWrapper<>(AreaDO.class)
                    .eq(AreaDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue())
                    .in(AreaDO::getParentCode, parentCodes)
                    .orderByAsc(AreaDO::getSort)
                    .orderByDesc(BaseDO::getCreateTime);
            List<AreaDO> areaDOS = areaMapper.selectList(queryWrapper);
            if (Collections3.isEmpty(areaDOS)) {
                flag = false;
                break;
            } else {
                temp.addAll(areaDOS);
                parentCodes.clear();
                parentCodes.addAll(areaDOS.stream().map(AreaDO::getCode).collect(Collectors.toList()));
            }
        }
        List<AreaDTO> result = JsonUtils.jsonToList(temp,AreaDTO.class);
        return result;
    }

    @Override
    public List<AreaDTO> queryArea(AreaQuery areaQuery) {
        if (Collections3.isEmpty(areaQuery.getNames())) {
            return Collections3.emptyList();
        }
        List<AreaDO> areaDos = areaMapper.selectList(new LambdaQueryWrapper<>(AreaDO.class)
                .eq(AreaDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue())
                .in(AreaDO::getName, areaQuery.getNames()));

        return JsonUtils.jsonToList(areaDos, AreaDTO.class);
    }

    @Override
    public List<AreaSimpleDTO> listByAreaIds(List<Long> ids) {
        List<AreaSimpleDTO> dtos = new ArrayList<>();

        List<AreaDO> areaDOS = areaMapper.selectBatchIds(ids);
        dtos = JsonUtils.jsonToList(areaDOS, AreaSimpleDTO.class);
        if (Collections3.isNotEmpty(areaDOS)) {
            List<String> parentCodes= areaDOS.stream().map(e->e.getParentCode()).collect(Collectors.toList());


            if (Collections3.isNotEmpty(parentCodes)){
                parentCodes = getParentCodes(parentCodes);
                LambdaQueryWrapper<AreaDO> wrapper = new LambdaQueryWrapper<>();
                wrapper.in(AreaDO::getCode,parentCodes);
                wrapper.eq(AreaDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue());
                List<AreaDO> parentDOS = areaMapper.selectList(wrapper);

                if (Collections3.isNotEmpty(parentDOS)) {
                    Map<String, AreaDO> parentMap = parentDOS.stream().collect(Collectors.toMap(AreaDO::getCode, Function.identity()));
                    for (AreaSimpleDTO areaSimpleDTO : dtos) {
                        setParent(areaSimpleDTO, parentMap);
                    }
                }
            }

        }
        return dtos;
    }

    @Override
    public AreaSimpleDTO listByAreaCode(String code) {
        List<AreaSimpleDTO> dtos = new ArrayList<>();

        LambdaQueryWrapper<AreaDO> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AreaDO::getCode,code);
        List<AreaDO> dos = areaMapper.selectList(wrapper);
        if (Collections3.isEmpty(dos)){
            return new AreaSimpleDTO();
        }
        wrapper.clear();
        dtos = JsonUtils.jsonToList(dos,AreaSimpleDTO.class);
        if (Collections3.isNotEmpty(dos)) {
            List<String> parentCodes= dos.stream().map(e->e.getParentCode()).collect(Collectors.toList());

            if (Collections3.isNotEmpty(parentCodes)){
                parentCodes = getParentCodes(parentCodes);

                wrapper.in(AreaDO::getCode,parentCodes);
                wrapper.eq(AreaDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue());
                List<AreaDO> parentDOS = areaMapper.selectList(wrapper);

                if (Collections3.isNotEmpty(parentDOS)) {
                    Map<String, AreaDO> parentMap = parentDOS.stream().collect(Collectors.toMap(AreaDO::getCode, Function.identity()));
                    for (AreaSimpleDTO areaSimpleDTO : dtos) {
                        setParent(areaSimpleDTO, parentMap);
                    }
                }
            }

        }

        return dtos.get(0);
    }


    @Override
    public List<AreaDTO> queryLowerAreasByCode(List<String> codes,List<AreaDTO> dtos,Boolean flag) {
        List<AreaDTO> areaDTOS = new ArrayList<>();
        LambdaQueryWrapper<AreaDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(AreaDO::getCode, codes);
        List<AreaDO> areaDOS = areaMapper.selectList(queryWrapper);
        if (Collections3.isNotEmpty(areaDOS)) {
            areaDTOS = JsonUtils.jsonToList(areaDOS,AreaDTO.class);
            AreaDO oneDo = areaDOS.get(0);
            //如果查询不需要包括镇级别的就传true
            if (flag && oneDo.getCode() != null && oneDo.getCode().length() > 6){
                return  Collections3.emptyList();
            }
            codes = areaDOS.stream().map(e->e.getParentCode()).collect(Collectors.toList());
            this.queryLowerAreasByCode(codes, dtos,flag);

        }
        dtos.addAll(areaDTOS);
        return dtos;
    }

    private void setParent(AreaSimpleDTO areaSimpleDTO,Map<String,AreaDO> parentMap){
        if (areaSimpleDTO == null){
            return ;
        }
        if (!"000000".equals(areaSimpleDTO.getParentCode())){
            areaSimpleDTO.setParentDTO(JsonUtils.fromJson(parentMap.get(areaSimpleDTO.getParentCode()),AreaSimpleDTO.class));
            setParent(areaSimpleDTO.getParentDTO(),parentMap);
        }
    }

    private List<String> getParentCodes(List<String> codes){
        if (Collections3.isEmpty(codes)){
            return Collections3.emptyList();
        }
        LambdaQueryWrapper<AreaDO> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(AreaDO::getCode,codes);
        wrapper.eq(AreaDO::getPublishStatus, PublishStatusEnum.ENABLE.getValue());
        List<AreaDO> dos = areaMapper.selectList(wrapper);
        if (Collections3.isNotEmpty(dos)){
            List<String> parentCodes= dos.stream().map(e->e.getParentCode()).collect(Collectors.toList());
            if (Collections3.isNotEmpty(parentCodes)) {
                codes.addAll(parentCodes);
                List<String> returnCodes = getParentCodes(parentCodes);
                if (Collections3.isNotEmpty(returnCodes)){
                    codes.addAll(returnCodes);
                }
            }
        }
        return codes;
    }

    private IAreaService getBean() {
        return SpringBeanUtil.getBean(IAreaService.class);
    }
}
